#pragma once
#ifdef __ANDROID__
#include <jni.h>
#include <android/log.h>
#include <unistd.h>// get pid/uid
#include <vector>
#include <string>

class AUtil {
public:
	static jobjectArray createJStringArray(JNIEnv *env, const std::vector<std::string>& strArr) {
		jclass stringClass = env->FindClass("java/lang/String");
		if (stringClass == nullptr)// cannot find the class
			return nullptr;

		jobjectArray stringArray = env->NewObjectArray(static_cast<jsize>(strArr.size()), stringClass, nullptr);
		for (size_t i = 0; i < strArr.size(); ++i) {
			jstring stringElement = env->NewStringUTF(strArr[i].c_str());
			env->SetObjectArrayElement(stringArray, static_cast<jsize>(i), stringElement);
			env->DeleteLocalRef(stringElement);
		}

		return stringArray;
	}

	static jintArray createJIntArray(JNIEnv *env, const std::vector<int>& intArr) {
		jsize size = intArr.size();
		jintArray result = env->NewIntArray(size);
		if (result == nullptr)// memory not enough
			return nullptr;
		env->SetIntArrayRegion(result, 0, size, intArr.data());
		return result;
	}

#define Get_IntArray_Ptr(jObject, fieldName) \
	AUtil::parseArray<int>(env, jObject, fieldName);
#define Get_FloatArray_Ptr(jObject, fieldName) \
	AUtil::parseArray<float>(env, jObject, fieldName);
#define Get_BoolArray_Ptr(jObject, fieldName) \
	AUtil::parseArray<bool>(env, jObject, fieldName);

#define RELEASE_ARRAY(...) \
	[&] { \
		auto func = [=](auto&&... args) { \
			(AUtil::releaseArray(env, args), ...); \
		}; \
		func(__VA_ARGS__); \
	}();

	template<typename E>
	static auto parseArray(JNIEnv *env, jobject jObject, const char* fieldName) {
		jclass objClass = env->GetObjectClass(jObject);
		if constexpr (std::is_same_v<E, int>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "[I");
			auto intArr = (jintArray) env->GetObjectField(jObject, field);
			auto arrayPtr = env->GetIntArrayElements(intArr, nullptr);
			return std::make_pair(arrayPtr, intArr);
		} else if constexpr (std::is_same_v<E, float>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "[F");
			auto floatArr = (jfloatArray) env->GetObjectField(jObject, field);
			auto arrayPtr = env->GetFloatArrayElements(floatArr, nullptr);
			return std::make_pair(arrayPtr, floatArr);
		} else if constexpr (std::is_same_v<E, bool>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "[Z");
			auto boolArr = (jbooleanArray) env->GetObjectField(jObject, field);
			auto arrayPtr = env->GetBooleanArrayElements(boolArr, nullptr);
			return std::make_pair(arrayPtr, boolArr);
		} else
			static_assert(false, "Unsupported type: only jint or jfloat or jboolean are allowed");
	}

#define Get_Int2DArray_Ptr(jObject, fieldName) \
	AUtil::parse2DArray<int>(env, jObject, fieldName);
#define Get_Float2DArray_Ptr(jObject, fieldName) \
	AUtil::parse2DArray<float>(env, jObject, fieldName);

#define RELEASE_2DARRAY(...) \
	[&] { \
		auto func = [=](auto&&... args) { \
			(AUtil::release2DArray(env, args), ...); \
		}; \
		func(__VA_ARGS__); \
	}();

	template<typename E>
	static auto parse2DArray(JNIEnv *env, jobject jObject, const char* fieldName) {
		jclass objClass = env->GetObjectClass(jObject);
		if constexpr (std::is_same_v<E, int>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "[[I");
			auto array2D = (jobjectArray)env->GetObjectField(jObject, field);
			jsize outerLength = env->GetArrayLength(array2D);
			std::vector<int*> pointers(outerLength);
			for (jsize i = 0; i < outerLength; i++) {
				auto innerArray = (jintArray)env->GetObjectArrayElement(array2D, i);
				pointers[i] = env->GetIntArrayElements(innerArray, nullptr);
			}
			return std::make_pair(pointers, array2D);
		} else if constexpr (std::is_same_v<E, float>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "[[F");
			auto array2D = (jobjectArray)env->GetObjectField(jObject, field);
			jsize outerLength = env->GetArrayLength(array2D);
			std::vector<float*> pointers(outerLength);
			for (jsize i = 0; i < outerLength; i++) {
				auto innerArray = (jfloatArray)env->GetObjectArrayElement(array2D, i);
				pointers[i] = env->GetFloatArrayElements(innerArray, nullptr);
			}
			return std::make_pair(pointers, array2D);
		} else
			static_assert(false, "Unsupported type: only jint or jfloat are allowed");
	}

	template<typename E, typename E_ARRAY>
	static void releaseArray(JNIEnv *env, const std::pair<E*, E_ARRAY>& arrayPair) {
		if constexpr (std::is_same_v<E, jint>)
			env->ReleaseIntArrayElements(arrayPair.second, arrayPair.first, 0);
		else if constexpr (std::is_same_v<E, jfloat>)
			env->ReleaseFloatArrayElements(arrayPair.second, arrayPair.first, 0);
		else if constexpr (std::is_same_v<E, jboolean>)
			env->ReleaseBooleanArrayElements(arrayPair.second, arrayPair.first, 0);
		else
			static_assert(false, "Unsupported type: only jint or jfloat or jboolean are allowed");
		env->DeleteLocalRef(arrayPair.second);
	}

	template<typename E>
	static void release2DArray(JNIEnv *env, const std::pair<std::vector<E*>, jobjectArray>& arrayPair) {
		auto [pointers, array2D] = arrayPair;
		jsize outerLength = env->GetArrayLength(array2D);
		if constexpr (std::is_same_v<E, int>) {
			for (jsize i = 0; i < outerLength; i++) {
				auto innerArray = (jintArray)env->GetObjectArrayElement(array2D, i);
				env->ReleaseIntArrayElements(innerArray, static_cast<int*>(pointers[i]), 0);
				env->DeleteLocalRef(innerArray);
			}
		} else if constexpr (std::is_same_v<E, float>) {
			for (jsize i = 0; i < outerLength; i++) {
				auto innerArray = (jfloatArray)env->GetObjectArrayElement(array2D, i);
				env->ReleaseFloatArrayElements(innerArray, static_cast<float*>(pointers[i]), 0);
				env->DeleteLocalRef(innerArray);
			}
		}
		env->DeleteLocalRef(array2D);
	}

#define Get_IntValue(jObject, fieldName) AUtil::parseValue<int>(env, jObject, fieldName)
#define Get_FloatValue(jObject, fieldName) AUtil::parseValue<float>(env, jObject, fieldName)
#define Get_BoolValue(jObject, fieldName) AUtil::parseValue<bool>(env, jObject, fieldName)

	template<typename E>
	static auto parseValue(JNIEnv *env, jobject jObject, const char *fieldName) {
		jclass objClass = env->GetObjectClass(jObject);
		if constexpr (std::is_same_v<E, int>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "I");
			return env->GetIntField(jObject, field);
		} else if constexpr (std::is_same_v<E, float>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "F");
			return env->GetFloatField(jObject, field);
		} else if constexpr (std::is_same_v<E, bool>) {
			jfieldID field = env->GetFieldID(objClass, fieldName, "Z");
			return env->GetBooleanField(jObject, field);
		} else
			static_assert(false, "Unsupported type: only jint or jfloat or jboolean are allowed");
	}
};
#endif